package com.ihai.spring.boot.sample.distributed.counter.manager;

import com.ihai.spring.boot.sample.distributed.counter.handler.NoticeHandler;
import com.ihai.spring.boot.sample.distributed.counter.model.Counter;
import com.ihai.spring.boot.sample.distributed.counter.model.CounterStore;
import com.ihai.spring.boot.sample.distributed.counter.model.ICounter;
import com.ihai.spring.boot.sample.distributed.counter.remote.Node;
import com.ihai.spring.boot.sample.distributed.counter.remote.NodeManager;
import com.ihai.spring.boot.sample.distributed.counter.util.JmxRemoteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * DESC
 *
 * @author 张驰
 * @date 2019/12/23
 */
public class CounterManager implements CounterManagerMBean {

    private static final Logger logger = LoggerFactory.getLogger(CounterManager.class);

    private static CounterManager instance;
    private static CounterManagerMBean masterCounterManagerMBean;
    private static ThreadLocal<AtomicInteger> failedTimes = new ThreadLocal<>();
    private CounterStore counterStore;

    private boolean isClustered = false;

    private CounterManager(boolean isClustered) {
        this.isClustered = isClustered;
        counterStore = new CounterStore();
    }


    public static synchronized void init(boolean isClustered) {
        if (instance == null) {
            instance = new CounterManager(isClustered);
        }
    }

    public static CounterManager getInstance() {
        if(instance == null){
            throw new RuntimeException("CacheManager is not initialized.");
        }
        return instance;
    }

    public void setClusters(List<Node> clusters) {
        if (isClustered) {
            NodeManager.setClusters(clusters);
            NodeManager.resetMaster();
        } else {
            throw new RuntimeException("This manager is not in cluster mode.");
        }
    }

    public void setCurrent(Node node) {
        if (isClustered) {
            if(NodeManager.getCurrent() == null){
                NodeManager.setCurrent(node);
                try {
                    JmxRemoteUtil.startJmxServer(node, "iHaiCounter");
                    JmxRemoteUtil.register(this,"iHaiCounter:name=counterManager");
                } catch (Exception e) {
                    logger.error("Exception starting RMI registry. Error was " + e.getMessage(), e);
                }
            }
        } else {
            throw new RuntimeException("This manager is not in cluster mode.");
        }
    }

    public void addCluster(Node node) {
        if (isClustered) {
            NodeManager.addCluster(node);
            NodeManager.resetMaster();
        } else {
            throw new RuntimeException("This manager is not in cluster mode.");
        }
    }

    public void removeCluster(Node node) {
        if (isClustered) {
            NodeManager.removeCluster(node);
            NodeManager.resetMaster();
        } else {
            throw new RuntimeException("This manager is not in cluster mode.");
        }
    }

    @Override
    public ICounter getCounter(String name) {
        return counterStore.get(name);
    }

    @Override
    public void asynFromRemote(String name, ICounter counter) {
        if(counter == null){
            if(counterStore.contain(name)){
                counterStore.remove(name);
            }
        }else{
            if(!counterStore.contain(counter.getName())){
                ICounter tmp = new Counter();
                tmp.setName(counter.getName());
                counterStore.addCounter(tmp);
            }
            counterStore.get(counter.getName()).asynFormRemote(counter);
        }
    }

    @Override
    public void addCounter(String name){
        ICounter counter = null;
        if(NodeManager.isMaster()){
            counter = counterStore.addCounter(name, -1);
            NoticeHandler.addNoticeElement(counter);
        }else{
            try {
                getMasterCountManager().addCounter(name);
                counterStore.addCounter(name, -1);
            }catch (Exception e){
                failedReset();
                logger.error("call master failed.", e);
                try {
                    addCounter(name);
                }catch (Exception e1){
                    logger.error("call master failed.", e1);
                }
            }
        }
    }

    @Override
    public void addCounter(String name, long timeout){
        ICounter counter = null;
        if(NodeManager.isMaster()){
            counter = counterStore.addCounter(name, timeout);
            NoticeHandler.addNoticeElement(counter);
        }else{
            try {
                getMasterCountManager().addCounter(name, timeout);
                counterStore.addCounter(name, timeout);
            }catch (Exception e){
                failedReset();
                logger.error("call master failed.", e);
                try {
                    addCounter(name, timeout);
                }catch (Exception e1){
                    logger.error("call master failed.", e1);
                }
            }
        }
    }

    @Override
    public void addCounter(ICounter counter){
        if(NodeManager.isMaster()){
            counterStore.addCounter(counter);
            NoticeHandler.addNoticeElement(counter);
        }else{
            try {
                getMasterCountManager().addCounter(counter);
                counterStore.addCounter(counter);
            }catch (Exception e){
                failedReset();
                logger.error("call master failed.", e);
                try {
                    addCounter(counter);
                }catch (Exception e1){
                    logger.error("call master failed.", e1);
                }
            }
        }
    }

    @Override
    public void removeCounter(String name){
        counterStore.remove(name);
        NoticeHandler.removeCounter(name);
    }

    public void isMaster(boolean isMaster) {
        counterStore.isMaster(isMaster);
    }

    public CounterManagerMBean getMasterCountManager(){
        if(masterCounterManagerMBean == null){
            masterCounterManagerMBean = JmxRemoteUtil.getMBean(NodeManager.getMaster(), "iHaiCounter", "iHaiCounter:name=counterManager", CounterManagerMBean.class);
        }
        return masterCounterManagerMBean;
    }

    private void failedReset(){
        if(failedTimes.get() == null){
            failedTimes.set(new AtomicInteger(1));
        }else{
            if(failedTimes.get().incrementAndGet() == 5){
                NodeManager.removeCluster(NodeManager.getMaster());
                NodeManager.resetMaster();
                masterCounterManagerMBean = null;
            }
        }
    }
}
